package cn.lg.soar.mvc.util;

import cn.lg.soar.common.util.data.DataUtil;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.common.util.listener.SpringUtils;
import cn.lg.soar.core.annotations.ApiClient;
import cn.lg.soar.core.annotations.ResourceScanIgnore;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.BiConsumer;

/**
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class RequestMappingUtils {

    public final static String REGX = "\\{[^/]+?}";

    /**
     * 获取控制器名称
     * @param ctrl
     * @return
     */
    public static String getApiName(Class<?> ctrl) {
        Api api = ctrl.getAnnotation(Api.class);
        if (api != null) {
            String[] tags = api.tags();
            for (String tag : tags) {
                if (StringUtil.isBlank(tag)) {
                    continue;
                }
                return tag;
            }
        }
        RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(ctrl, RequestMapping.class);
        if (mapping == null) {
            return "";
        }
        return mapping.name();
    }

    /**
     * 获取路径名称
     * @param handlerMethod
     * @return
     */
    private static String getPathName(HandlerMethod handlerMethod) {
        Class<?> ctrl = handlerMethod.getBeanType();
        String apiName = getApiName(ctrl) +":";

        Method method = handlerMethod.getMethod();
        ApiOperation operation = method.getAnnotation(ApiOperation.class);
        if (operation != null && !StringUtil.isBlank(operation.value())) {
            return apiName + operation.value();
        }
        RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
        if (mapping != null) {
            return apiName + mapping.name();
        }
        return apiName;
    }

    /**
     * 获取所有映射路径
     * @return
     */
    public static Map<String, String> getAllMappingPath() {
        RequestMappingHandlerMapping requestMappingHandlerMapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        Set<Map.Entry<RequestMappingInfo, HandlerMethod>> entries = handlerMethods.entrySet();
        Map<String, String> map = new HashMap<>(entries.size());
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : entries) {
            HandlerMethod handlerMethod = entry.getValue();
            ApiClient apiClient = handlerMethod.getBeanType().getAnnotation(ApiClient.class);
            if (apiClient != null) {
                continue;
            }
            String pathName = getPathName(handlerMethod);

            RequestMappingInfo mappingInfo = entry.getKey();
            RequestMethodsRequestCondition methodsCondition = mappingInfo.getMethodsCondition();
            Set<RequestMethod> methods = methodsCondition.getMethods();
            Set<String> patternValues = mappingInfo.getPatternValues();
            for (RequestMethod method : methods) {
                String name = method.name();
                for (String patternValue : patternValues) {
                    map.put(name + patternValue.replaceAll(REGX, StringUtil.ASTERISK), pathName);
                }
            }
        }
        return map;
    }

    /**
     * 解析 RequestMapping
     * @param mapping
     * @return
     */
    public static Data parseMapping(RequestMapping mapping) {
        Data data = new Data();
        if (mapping == null) {
            data.paths = new HashSet<>();
            data.methods = new HashSet<>();
            data.name = null;
            return data;
        }
        data.name = mapping.name();
        Set<String> paths = new HashSet<>();
        paths.addAll(Arrays.asList(mapping.value()));
        paths.addAll(Arrays.asList(mapping.path()));
        data.paths = paths;

        data.methods = new HashSet<>();
        RequestMethod[] method = mapping.method();
        for (RequestMethod requestMethod : method) {
            data.methods.add(requestMethod.name());
        }
        return data;
    }

    /**
     * 扫描控制器解析路径
     * @param basePackages
     * @param consumer
     */
    public static void scanControllerParsePath(Collection<String> basePackages, BiConsumer<Data, List<Data>> consumer) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
        for (String packages : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(packages);
            for (BeanDefinition beanDefinition : candidateComponents){
                try {
                    Class<?> aClass = Class.forName(beanDefinition.getBeanClassName());
                    if (aClass.getAnnotation(ResourceScanIgnore.class) != null) {
                        continue;
                    }
                    RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(aClass, RequestMapping.class);
                    Data data = RequestMappingUtils.parseMapping(mapping);
                    data.raw = aClass;
                    Api api = aClass.getAnnotation(Api.class);
                    if (api != null) {
                        String name = api.tags()[0];
                        if (!StringUtil.isBlank(name)) {
                            data.name = name;
                        }
                    }
                    RequestMappingUtils.handleMethod(data, aClass, consumer);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 扫描控制器解析路径
     * @param basePackages
     * @param consumer
     */
    public static void scanControllerParsePath(String basePackages, BiConsumer<Data, List<Data>> consumer) {
        scanControllerParsePath(DataUtil.arrayToList(basePackages.split(",")), consumer);
    }

    /**
     * 解析方法的路径
     * @param ctrl
     * @param aClass
     * @param consumer
     */
    private static void handleMethod(Data ctrl, Class<?> aClass, BiConsumer<Data, List<Data>> consumer) {
        Method[] methods = aClass.getMethods();
        Set<Method> set = new HashSet<>();
        // 去重
        for (Method method : methods) {
            if (method.getAnnotation(ResourceScanIgnore.class) != null) {
                continue;
            }
            RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
            if (mapping == null) {
                continue;
            }
            set.add(method);
        }
        // 转换
        List<Data> list = new ArrayList<>();
        for (Method method : set) {
            RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
            Data data = RequestMappingUtils.parseMapping(mapping);
            data.raw = method;
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                String value = apiOperation.value();
                if (!StringUtil.isBlank(value)) {
                    data.name = value;
                }
            }
            list.add(data);
        }
        // 生成权限
        consumer.accept(ctrl, list);
    }

    /**
     * 扫描控制器解析获取 RequestMapping
     * @param basePackage
     * @param consumer
     */
    public static void sacnControllerRequestMapping(String basePackage, BiConsumer<RequestMapping, Set<RequestMapping>> consumer) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition beanDefinition : candidateComponents){
            try {
                Class<?> aClass = Class.forName(beanDefinition.getBeanClassName());
                RequestMappingUtils.handleMethodRequestMapping(AnnotatedElementUtils.getMergedAnnotation(aClass, RequestMapping.class), aClass, consumer);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 读取方法的 RequestMapping
     * @param ctrl
     * @param aClass
     * @param consumer
     */
    private static void handleMethodRequestMapping(RequestMapping ctrl, Class<?> aClass, BiConsumer<RequestMapping, Set<RequestMapping>> consumer) {
        Method[] methods = aClass.getMethods();
        Set<RequestMapping> set = new HashSet<>();
        // 去重
        for (Method method : methods) {
            RequestMapping mapping = AnnotatedElementUtils.getMergedAnnotation(method, RequestMapping.class);
            if (mapping == null) {
                continue;
            }
            set.add(mapping);
        }
        consumer.accept(ctrl, set);
    }

    @lombok.Data
    public static class Data {

        private String name;

        private Set<String> paths;

        private Set<String> methods;

        private Object raw;

    }
}
